﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System
{
    /// <summary>
    /// 农历数据结构
    /// </summary>
    public struct Lunar
    {
        public int Year; //农历年
        public int Month; //农历月
        public int Day; //农历日
        public bool IsLeapMonth; //IsLeapMonth指出本月是否闰月
    }

    /// <summary>
    /// 农历 API
    /// 用法：  var d = DateTime.GetLunarData(); string dya = d.ToLongTimeString(t);
    /// </summary>
    public static class LunarAPI
    {
        /******************************************************************************************************/

        #region 常数及及其初始化

        public const double TropicalYear = 31556925974.7; //回归年周期(单位:毫秒)

        /// <summary>
        /// 开始计算农历的年份
        /// </summary>
        public const int StartYear = 1900;
        /// <summary>
        /// 结束介绍农历的年份
        /// </summary>
        public const int EndYear = 2050;

        private static readonly uint[] _lunarData; //农历数据
        private static readonly uint[] _sTermData; //24节气数据
        private static readonly string[] _gan; //天干
        private static readonly string[] _zhi; //地支
        private static readonly string[] _animals; //十二生肖
        private static readonly string[] _solarTerm; //二十四节气
        private static readonly string[] _cnNum; //中文数字
        private static readonly string[] _cnWeek;

        static LunarAPI()
        {
            //数组_lunarInfo 低四位是每年的闰月月份,中间12位是12个月的天数(1=30天,0=29天) 高位是闰月的天数
            _lunarData = new uint[]
	                    {
	                        0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2, //1900-1909
	                        0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977, //1910-1919
	                        0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970, //1920-1929
	                        0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950, //1930-1939
	                        0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557, //1940-1949
	                        0x06ca0,0x0b550,0x15355,0x04da0,0x0a5d0,0x14573,0x052d0,0x0a9a8,0x0e950,0x06aa0, //1950-1959
	                        0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0, //1960-1969
	                        0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b5a0,0x195a6, //1970-1979
	                        0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570, //1980-1989
	                        0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0, //1990-1999
	                        0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5, //2000-2009
	                        0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930, //2010-2019
	                        0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530, //2020-2029
	                        0x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45, //2030-2039
	                        0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0, //2040-2049
	                        0x05d53 //2050
	                    };
            //每个节气与小寒相差的时间 (单位:分)
            _sTermData = new uint[]
	                    {
	                        0,21208,
	                        42467,63836,85337,107014,128867,150921,
	                        173149,195551,218072,240693,263343,285989,
	                        308563,331033,353350,375494,397447,419210,
	                        440795,462224,483532,504758
	                    };
            //天干、地支、与十二生肖等
            _gan = new string[] { "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" };
            _zhi = new string[] { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };
            _animals = new string[] { "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪" };
            _solarTerm = new string[]{"","立春","雨水","惊蛰","春分","清明","谷雨",
	                                    "立夏","小满","芒种","夏至","小暑","大暑",
	                                    "立秋","处暑","白露","秋分","寒露","霜降",
	                                    "立冬","小雪","大雪","冬至","小寒","大寒"};
            _cnNum = new string[] { "○", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二" };
            _cnWeek = new string[] { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };

        }
        #endregion 常数及及其初始化

        /******************************************************************************************************/

        /// <summary>
        /// 计算Year年的第Solar(1=立春) 个节气对应的公元日期 (精确到分)
        /// </summary>
        /// <param name="Year"></param>
        /// <param name="Solor"></param>
        /// <returns></returns>
        private static DateTime GetSolarDateTime(int Year, int Solor)
        {
            int n = (Solor + 1) % 24;
            double offset = (TropicalYear * (Year - 1900)) + 60000.0 * _sTermData[n];
            DateTime date = new DateTime(1900, 1, 6, 2, 5, 0);
            DateTime test = date.AddMilliseconds(offset);
            return test;
        }
        /// <summary>
        /// 计算公元年月日对应的节气,返回值0-24,0表示不是节气
        /// </summary>
        /// <param name="Year"></param>
        /// <param name="Month"></param>
        /// <param name="Day"></param>
        /// <returns></returns>
        private static int GetSolorTerm(int Year, int Month, int Day)
        {
            int solor = (Month * 2 + 20) % 24 + 1;
            if (Day > 15) solor++; //计算该日可能的节气
            DateTime date = GetSolarDateTime(Year, solor);
            if (date.Day != Day)
            {
                return 0;
            }
            return solor;
        }

        /// <summary>
        /// 计算农历Year年的闰月月份
        /// </summary>
        /// <param name="lYear"></param>
        /// <returns></returns>
        private static int GetLeapMonth(int lYear)
        {
            if (lYear < StartYear) return 0;
            return (int)(_lunarData[lYear - StartYear] & 0x0f);
        }
        /// <summary>
        /// 计算农历年月对应的天数(29或30天), 如果lMonth为闰月，
        /// 高字为第二个lMonth月的天数，否则高字为0
        /// </summary>
        /// <param name="lYear"></param>
        /// <param name="lMonth"></param>
        /// <returns></returns>
        private static uint GetLunarMonthDays(int lYear, int lMonth)
        {
            if (lYear < StartYear) return 30;

            uint info = _lunarData[lYear - StartYear];

            int bit = 16 - lMonth;
            uint low = 29 + ((info >> bit) & 0x01);

            if (lMonth == GetLeapMonth(lYear))
            {
                uint high = 29 + (info >> 16);
                return (high << 16) | low;
            }

            return low;
        }
        /// <summary>
        /// 计算农历lYear年的总天数
        /// </summary>
        /// <param name="lYear"></param>
        /// <returns></returns>
        private static uint GetLunarYearDays(int lYear)
        {
            uint days = 0;
            for (int m = 1; m <= 12; m++)
            {
                uint tmp = GetLunarMonthDays(lYear, m);
                days += tmp >> 16;
                days += tmp & 0xff;
            }
            return days;
        }
        /// <summary>
        /// 计算从公元1900年1月1日过iSpanDays天后的阴历日期
        /// </summary>
        /// <param name="spanDays"></param>
        /// <returns></returns>
        private static Lunar CalcLunarDate(uint spanDays)
        {
            //阳历1900年1月31日为阴历1900年正月初一
            //阳历1900年1月1日到1月31日共有30天
            Lunar lunar = new Lunar();
            lunar.IsLeapMonth = false;

            if (spanDays < 30)
            {
                lunar.Year = StartYear - 1;
                lunar.Month = 12;
                lunar.Day = (int)spanDays + 1;
                return lunar;
            }
            //下面从阴历1900年正月初一算起
            spanDays -= 30;
            lunar.Year = StartYear;
            lunar.Month = 1;
            lunar.Day = 1;
            //计算年
            uint tmp = GetLunarYearDays(lunar.Year);
            while (spanDays >= tmp)
            {
                spanDays -= tmp;
                tmp = GetLunarYearDays(++lunar.Year);
            }
            //计算月
            tmp = GetLunarMonthDays(lunar.Year, lunar.Month) & 0xff;
            while (spanDays >= tmp)
            {
                spanDays -= tmp;
                lunar.IsLeapMonth = (lunar.Month == GetLeapMonth(lunar.Year));
                if (lunar.IsLeapMonth)
                {
                    tmp = GetLunarMonthDays(lunar.Year, lunar.Month) >> 16;
                    if (spanDays < tmp) break;

                    spanDays -= tmp;
                }
                tmp = GetLunarMonthDays(lunar.Year, ++lunar.Month) & 0xff;
            }
            //计算日
            lunar.Day += (int)spanDays;
            return lunar;
        }



        /******************************************************************************************************/
        /// <summary>
        /// 计算公历日期对应的农历日期
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static Lunar GetLunarDate(DateTime date)
        {
            if (date.Year < StartYear || date.Year > EndYear)
            {
                throw new ArgumentException(string.Format("计算的农历年份超出可计算范围,可计算年份为{0}年-{1}年", StartYear, EndYear));
            }
            TimeSpan span = date.Subtract(new DateTime(StartYear, 1, 1));
            return CalcLunarDate((uint)span.Days);
        }

        /// <summary>
        /// 取农历年份的干支
        /// </summary>
        /// <param name="lYear"></param>
        /// <returns></returns>
        public static string GetLunarYearString(this Lunar l)
        {
            int cycle = (l.Year - 1864) % 60;
            return _gan[cycle % 10] + _zhi[cycle % 12];
        }
        /// <summary>
        /// 取农历年份的生肖
        /// </summary>
        /// <param name="lYear"></param>
        /// <returns></returns>
        public static string GetYearAnimalString(this Lunar l)
        {
            int n = (l.Year - 1864) % 12;
            return _animals[n];
        }
        /// <summary>
        /// 取农历月份中文字串
        /// </summary>
        /// <param name="l"></param>
        /// <param name="isFull">是否获取完整的月份描述，如果是那么表示成（例如：闰七月大），如果不是那么表示成（例如：七月）</param>
        /// <returns></returns>
        public static string GetLunarMonthString(this Lunar l, bool isFull = false)
        {
            int lMonth = l.Month;
            int lYear = l.Year;
            if (isFull)
            {
                bool isLeap = (lMonth == GetLeapMonth(lYear));
                uint dd1 = GetLunarMonthDays(lYear, lMonth);
                uint dd = (isLeap) ? dd1 >> 16 : dd1 & 0xff;
                string leap = isLeap ? "闰" : "";
                string large = (dd == 30) ? "大" : "小";
                string month = (lMonth == 1) ? "正" : _cnNum[lMonth];
                return leap + month + "月" + large;
            }
            else
            {
                return string.Format("{0}月", (lMonth == 1) ? "正" : _cnNum[lMonth]);
            }
        }

        /// <summary>
        /// 获取农历的短时间字符串，例如：七月二十八
        /// </summary>
        /// <param name="l"></param>
        /// <returns></returns>
        public static string ToShortTimeString(this Lunar l)
        {
            return string.Format("{0}{1}", l.GetLunarMonthString(), l.GetLunarDayString());
        }
        /// <summary>
        /// 获取农历的长时间字符串，例如：壬辰年(龙)七月廿八
        /// </summary>
        /// <param name="l"></param>
        /// <returns></returns>
        public static string ToLongTimeString(this Lunar l)
        {
            return string.Format("{0}({1})年{2}", l.GetLunarYearString(), l.GetYearAnimalString(), l.ToShortTimeString());
        }
        /// <summary>
        /// 获取农历的长时间字符串（包括节气），例如：壬辰年(龙)七月廿八(大暑)
        /// </summary>
        /// <param name="l"></param>
        /// <param name="time"></param>
        /// <returns></returns>
        public static string ToLongTimeString(this Lunar l, DateTime time)
        {
            string solarTerm = l.GetSolarString(time);
            if (!string.IsNullOrEmpty(solarTerm))
            {
                return string.Format("{0}({1})年{2}({3})", l.GetLunarYearString(), l.GetYearAnimalString(), l.ToShortTimeString(), solarTerm);
            }
            return string.Format("{0}({1})年{2}", l.GetLunarYearString(), l.GetYearAnimalString(), l.ToShortTimeString());
        }

        /// <summary>
        /// 取农历日份中文字串
        /// </summary>
        /// <param name="lDay"></param>
        /// <returns></returns>
        public static string GetLunarDayString(this Lunar l)
        {
            int lDay = l.Day;
            if (lDay < 10) return "初" + _cnNum[lDay];
            if (lDay == 10) return "初十";
            if (lDay < 20) return "十" + _cnNum[lDay % 10];
            if (lDay == 20) return "二十";
            if (lDay < 30) return "廿" + _cnNum[lDay % 10];
            return "三十";
        }

        /// <summary>
        /// 取二十四节气中文字串
        /// </summary>
        /// <param name="solar"></param>
        /// <returns></returns>
        public static string GetSolarString(this Lunar l, DateTime time)
        {
            int solar = GetSolorTerm(time.Year, time.Month, time.Day);
            return _solarTerm[solar];
        }

        /// <summary>
        /// 取星期中文字串
        /// </summary>
        /// <param name="week"></param>
        /// <returns></returns>
        public static string GetWeekString(int week)
        {
            return _cnWeek[week];
        }
    }

}
